using System;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Caramel
{
	public static class StringExtensions
	{
		public static string FormatWith(this string instance, params object[] args)
		{
			return String.Format(instance, args);
		}

		public static bool IsAlphabetic(this string instance)
		{
			return
				!instance.
					ToCharArray().
					Any(c => !Char.IsLetter(c));
		}

		public static bool IsDigit(this string instance)
		{
			return
				!instance.
					ToCharArray().
					Any(c => !Char.IsDigit(c));
		}

		public static bool IsNumeric(this string instance)
		{
			double result;

			return Double.TryParse(instance, out result);
		}

		public static string RemoveLast(this string instance, string value)
		{
			var index = instance.LastIndexOf(value);

			if (index >= 0)
			{
				return instance.Substring(0, index) + instance.Substring(index + value.Length);
			}

			return instance;
		}

		public static string ToSeparatedWords(this string instance)
		{
			return 
				Regex.
					Replace(instance, "([A-Z][a-z])", " $1").
					Trim();
		}

		public static string FormatWith(this string format, object source)
		{
			if (format == null)
			{
				throw new ArgumentNullException("format");
			}

			var result = new StringBuilder(format.Length * 2);

			using (var reader = new StringReader(format))
			{
				var expression = new StringBuilder();
				var @char = -1;
				var state = State.OutsideExpression;

				do
				{
					switch (state)
					{
						case State.OutsideExpression:
							@char = reader.Read();

							switch (@char)
							{
								case -1:
									state = State.End;
									break;

								case '{':
									state = State.OnOpenBracket;
									break;

								case '}':
									state = State.OnCloseBracket;
									break;

								default:
									result.Append((char)@char);
									break;
							}

							break;

						case State.OnOpenBracket:
							@char = reader.Read();

							switch (@char)
							{
								case -1:
									throw new FormatException();

								case '{':
									result.Append('{');
									state = State.OutsideExpression;
									break;

								default:
									expression.Append((char)@char);
									state = State.InsideExpression;
									break;
							}

							break;

						case State.InsideExpression:
							@char = reader.Read();

							switch (@char)
							{
								case -1:
									throw new FormatException();

								case '}':
									result.Append(OutExpression(source, expression.ToString()));
									expression.Length = 0;
									state = State.OutsideExpression;
									break;

								default:
									expression.Append((char)@char);
									break;
							}

							break;

						case State.OnCloseBracket:
							@char = reader.Read();

							switch (@char)
							{
								case '}':
									result.Append('}');
									state = State.OutsideExpression;
									break;

								default:
									throw new FormatException();
							}

							break;

						default:
							throw new InvalidOperationException("Invalid state.");
					}
				} 
				while (state != State.End);
			}

			return result.ToString();
		}

		private enum State
		{
			OutsideExpression,
			OnOpenBracket,
			InsideExpression,
			OnCloseBracket,
			End
		}

		private static string OutExpression(object source, string expression)
		{
			var format = "";
			var colonIndex = expression.IndexOf(':');

			if (colonIndex > 0)
			{
				format = expression.Substring(colonIndex + 1);
				expression = expression.Substring(0, colonIndex);
			}

			try
			{
				if (String.IsNullOrEmpty(format))
				{
					return (DataBinder.Eval(source, expression) ?? "").ToString();
				}

				return DataBinder.Eval(source, expression, "{0:" + format + "}") ?? "";
			}
			catch (HttpException)
			{
				throw new FormatException();
			}
		}
	}
}
